package p2psvideo;

import android.annotation.SuppressLint;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.AudioTrack.OnPlaybackPositionUpdateListener;
import android.media.MediaFormat;
import android.util.Log;

import java.util.LinkedList;
import java.util.List;

@SuppressLint("NewApi")
class SoundPlayer implements OnPlaybackPositionUpdateListener
{
	private Thread _playThread;	
	private volatile boolean _quit;
	private long _timeStamp=-1;
	private long _timeStampSetTime=-1;
	private int _queueBytes=0;
	private volatile boolean _paused;
	private int _maxSize = 512*1024;
	private int _latency = 0;
	private int _bufferLatency=0;
	private boolean _usingTrack;		
	private AudioTrack _audioTrack;
	NewTrackInfo _currentInputInfo;
	
	static class SoundPlayerTrack extends AudioTrack
	{
		public SoundPlayerTrack(int streamType, 
                int sampleRateInHz, 
                int channelConfig, 
                int audioFormat, 
                int bufferSizeInBytes, 
                int mode)
		{
			super(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode);
		}
		public int getHardwareFrameCount()
		{
			return this.getNativeFrameCount();
		}		
	}
	
	static public interface OnAudioFeed
	{
		void onAudioFeed();
	}
	private OnAudioFeed _onAudioFeed;
	
	private List<SoundPlayerBuffer> _buffers = new LinkedList<SoundPlayerBuffer>();
	private int _minSize;
	
	///精确时间戳计算队列
	static class TimePosQueue
	{
		static class TimePosNode
		{
			public long timestamp;
			public int pos;
			public int end;
		}
		private List<TimePosNode> _times = new LinkedList<TimePosNode>();
		private int _pos;
		private int _sampleRate;
		
		TimePosQueue(int sampleRate)
		{
			_sampleRate = sampleRate;
		}
		
		
		///输入一帧
		public void add(long timestamp, int samples)
		{
			TimePosNode e = new TimePosNode();
			e.timestamp = timestamp;
			e.pos = _pos;			
			_pos += samples;
			e.end = _pos;
			_times.add(e);
		}
		static int cmp(int v1, int v2)
		{
			return v1 - v2;
		}
		///根据pos退帧，并计算时间戳
		public long pop(int pos)
		{
			int toDelete=-1;
			long t = -1;
			for(TimePosNode e: _times) {
				if (cmp(pos, e.pos) >= 0) {
					if (cmp(pos, e.end) <= 0) { //定位到了节点
						t = e.timestamp + (pos - e.pos)*1000000L / _sampleRate;
						break;
					} else {
						toDelete++;
					}
				} else break;
			}
			while (toDelete >= 0) {
				_times.remove(0);
				toDelete--;
			}
			return t;
		}
		
		public long popToLast()
		{
			while(_times.size() > 1) {
				_times.remove(0);
			}
			if (_times.size() == 1) {
				return _times.get(0).timestamp;
			} else {
				return -1;
			}
		}
	}
	
	
	
	private TimePosQueue _times;
	
	static class PlayThread extends Thread {
		private SoundPlayer _player;
		PlayThread(SoundPlayer player)
		{
			_player = player;
		}
		@Override
		public void run() {
			_player._doRun();
			_player = null;
			super.run();			
			try {
				this.finalize();
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}			
	}
	
	public SoundPlayer(OnAudioFeed onAudioFeed, int latency) {
		_onAudioFeed = onAudioFeed;
		_latency = latency;
		_playThread = new PlayThread(this);
		_playThread.start();		
	}
	
	void release()
	{
		_quit = true;
		try {
			_playThread.interrupt();
		} catch(Exception e) {
			e.printStackTrace();
		}
		try {
			_playThread.join();
		} catch(Exception e) {
			e.printStackTrace();
		}
		synchronized(this) {
			_playThread = null;
		}
		_buffers.clear();
		_audioTrack = null;		
		_onAudioFeed = null;
	}
	
	void rebuild(MediaFormat format)
	{
		int sr = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
		int chnls = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);			
		int chnlCfg = 0;
		try {
			if (format.containsKey(MediaFormat.KEY_CHANNEL_MASK)) {
				chnlCfg = format.getInteger(MediaFormat.KEY_CHANNEL_MASK);
			}
		} catch(Error e) {
			e.printStackTrace();
		}
		if (chnlCfg == 0) {
			if (chnls == 1) {
				chnlCfg = AudioFormat.CHANNEL_OUT_MONO;
			} else if (chnls == 2) {
				chnlCfg = AudioFormat.CHANNEL_OUT_STEREO;
			} else if (chnls == 3) {
				chnlCfg = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_LOW_FREQUENCY;
			} else if (chnls == 4) {
				chnlCfg = AudioFormat.CHANNEL_OUT_QUAD;
			} else if (chnls == 5) {
				chnlCfg = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_LOW_FREQUENCY; 
			} else if (chnls == 6) {
				chnlCfg = AudioFormat.CHANNEL_OUT_5POINT1;
			} else if (chnls == 8) {
				chnlCfg = AudioFormat.CHANNEL_OUT_7POINT1;
			} else {
				chnlCfg = AudioFormat.CHANNEL_OUT_DEFAULT;
			}
		}
		NewTrackInfo info=new NewTrackInfo();
		info.chnlCfg = chnlCfg;
		info.sampleRate = sr;
		info.chnls = chnls;
		SoundPlayerBuffer buf = new SoundPlayerBuffer(null, 0);
		buf.trackInfo = info;
		_usingTrack = true;
		synchronized(_buffers) {
			_currentInputInfo = info;
			_buffers.add(buf);
		}				
	}
	void postBuffer(SoundPlayerBuffer buf)
	{
		synchronized(_buffers) {
			_buffers.add(buf);
			_queueBytes += buf.buffer.length;
		}
	}
	
	int getPendingBytes()
	{
		return _queueBytes;
	}
	
	int getPendingDuration()
	{
		synchronized(_buffers) {
			if (_currentInputInfo == null) return 0;
			return (int)(_queueBytes*1000L / (_currentInputInfo.chnls * 2 * _currentInputInfo.sampleRate)); 
		}
	}
	
	boolean setTimeStamp(long t)
	{
		if (_usingTrack) return false;
		_timeStamp = t;
		_timeStampSetTime = System.nanoTime();		
		return true;
	}
	void setAudioEnd()
	{
		_usingTrack = false;
	}
	
	long getFrameTimeStamp()
	{
		return _timeStamp;
	}
	
	long getTimeStamp()
	{	
		synchronized(_buffers) {
			if (_timeStamp == -1) return -1;		
			if (_timeStampSetTime == -1) return -1;
			return (System.nanoTime() - _timeStampSetTime)/1000 + _timeStamp;
		}
	}
	
	boolean full()
	{
		synchronized(_buffers) {
			return _queueBytes > _maxSize;
		}
	}
	
	void pause()
	{
		synchronized(this) {
			_paused = true;
			if (_playThread != null) {
				_playThread.interrupt();
			}
			if (_audioTrack != null) {
				try {
					_audioTrack.pause();
				} catch(Error e) {
					e.printStackTrace();
				} catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	void resume()
	{
		synchronized(this) {
			_paused = false;
			if (_playThread != null) {
				_playThread.interrupt();
			}
			if (_audioTrack != null) {
				try {
					_audioTrack.play();
				} catch(Error e) {
					e.printStackTrace();
				} catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	boolean empty()
	{
		synchronized(_buffers) {
			return _buffers.isEmpty();
		}
	}
	
	private void _doRun()
	{
		SoundPlayerTrack track = null;
		TimePosQueue times = null;
		NewTrackInfo info=null;
		try {
			android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
		} catch(Exception e) {
			e.printStackTrace();
		}
		while(!_quit) {
			SoundPlayerBuffer buf = null;
			synchronized(_buffers) {
				if (!_buffers.isEmpty()) {
					buf = _buffers.get(0);
					_buffers.remove(0);
					if (buf.buffer != null) _queueBytes -= buf.buffer.length;
				}
			}			
			if (buf != null) {
				if (buf.buffer == null && buf.trackInfo != null) { //更换AudioTrack	
					if (track != null) { //有前一个track, 等待前一个track把缓冲区吃完再释放
						try {
							track.flush();
							track.stop();
						} catch(Exception e) {
							e.printStackTrace();
						}
						try {
							track.release();
						} catch(Error e) {
							e.printStackTrace();
						}
						track = null;
					}
					info = buf.trackInfo;
					int minsize=AudioTrack.getMinBufferSize(buf.trackInfo.sampleRate, buf.trackInfo.chnlCfg, AudioFormat.ENCODING_PCM_16BIT);
					_minSize = minsize;
					Log.v("SoundPlayer", "minsize="+minsize);
					try {
						track = new SoundPlayerTrack(AudioManager.STREAM_MUSIC, buf.trackInfo.sampleRate, buf.trackInfo.chnlCfg, AudioFormat.ENCODING_PCM_16BIT, minsize, AudioTrack.MODE_STREAM);
						//track.setPlaybackPositionUpdateListener(this);
						//track.setPositionNotificationPeriod(minsize/(2*info.chnls*8));
						track.play();			
						_bufferLatency = (int)(track.getHardwareFrameCount() * 1000000L / buf.trackInfo.sampleRate);
						_maxSize = minsize * 50;
						times = new TimePosQueue(buf.trackInfo.sampleRate);
						_usingTrack = true;
					} catch(Error e) {
						e.printStackTrace();
					}
					synchronized(this) {
						_audioTrack = track;
						_times = times;
					}
					if (track == null) _usingTrack = false;
				}
			}				
			if (track != null) {
				if (buf != null && buf.buffer != null) {
					int offset = 0;
					int n = buf.buffer.length;
					int frameSize = info.chnls * 2;
					int blockFrames = _minSize / frameSize;
					if (_latency == -1) {
						if (blockFrames > 256) blockFrames = 256;
					}
					//每次最多写blockFrames组采样						
					int blockSize = frameSize * blockFrames;
					int blocks =  n / blockSize;
					long ts = buf.timeStamp;
					for(int i=0; i<blocks && !_quit; ++i) {				
						synchronized(this) {
							_times.add(ts, blockFrames);
						}
						long twrite = System.nanoTime();
						track.write(buf.buffer, offset, blockSize);						
						offset += blockSize;
						ts = buf.timeStamp + (i+1) * blockFrames * 1000000L / info.sampleRate;						
						if (_latency != -1) {
							onPeriodicNotification(track);
						} else if (System.nanoTime() - twrite > 1000000L) { //大于1ms我们假定track.write阻塞
							onPeriodicNotification(track);
						}
					}
					int leftFrames = (n - offset)/frameSize;
					if (leftFrames != 0 && !_quit) {
						synchronized(this) {
							_times.add(ts, leftFrames);
						}
						long twrite = System.nanoTime();
						track.write(buf.buffer, offset, leftFrames * frameSize);
						if (_latency != -1) {
							onPeriodicNotification(track);
						} else if (System.nanoTime() - twrite > 1000000L) { //大于1ms我们假定track.write阻塞
							onPeriodicNotification(track);
						}
					}					
				}
			}
			while (_paused && !_quit) {
				try {
					Thread.sleep(5);
				} catch (InterruptedException e) {
					//e.printStackTrace();
				}
			}
			if (buf == null && !_quit) {
				try {
					Thread.sleep(5);
				} catch (InterruptedException e) {
					//e.printStackTrace();
				}				
			}
		}
		synchronized(this) {
			_audioTrack = null;
		}
		if (track != null) {
			try {
				track.flush();
				track.stop();
			} catch(Exception e) {
				e.printStackTrace();
			}
			try {
				track.release();
			} catch(Error e) {
				e.printStackTrace();
			}
			track = null;
		}
	}

	@Override
	public void onMarkerReached(AudioTrack track) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onPeriodicNotification(AudioTrack track) {
		boolean fire = false;
		synchronized(this) {
			if (track != _audioTrack || _times == null) {
				return;
			}			
			long t = -1;
			if (_latency != -1) {
				t = _times.pop(track.getPlaybackHeadPosition());
			} else {
				t = _times.popToLast();
			}
			if (t != -1) {
				long ct = System.nanoTime();
				if (_timeStampSetTime != -1 && ct - _timeStampSetTime < 5000000) {
					fire = true;
				} else {			
					if (_latency != -1) {
						t -= _latency;
					} else {
						t -= _bufferLatency;
					}
					_timeStamp = t;					
					_timeStampSetTime = ct; 
					fire = true;
				}
			}
		}
		if (fire && _onAudioFeed != null) {
			_onAudioFeed.onAudioFeed();
		}
	}
	
}